import functools
from collections import defaultdict, deque

import yaml
from selenium.webdriver.common.by import By

from utils.logoperator import LogOperator

logger = LogOperator(__name__)


def memoize_navigation_path(method):
    @functools.wraps(method)
    def wrapper(self, start_page, target_page):
        MAX_NAVIGATION_PATHS_CACHE = 100
        if len(self._navigation_paths) > MAX_NAVIGATION_PATHS_CACHE:
            oldest_key = next(iter(self._navigation_paths))
            del self._navigation_paths[oldest_key]

        navigation_key = (start_page, target_page)
        if navigation_key in self._navigation_paths:
            return self._navigation_paths[navigation_key]

        navigation_steps = method(self, start_page, target_page)
        self._navigation_paths[navigation_key] = navigation_steps
        return navigation_steps

    return wrapper


class UIConfigLoader:
    def __init__(self, config_path):
        self.config_path = config_path
        self._config = self._load_yaml_config()

    def _load_yaml_config(self):
        try:
            with open(self.config_path, 'r+', encoding='utf-8') as f:
                yaml_dict = yaml.safe_load(f) or {}
                return yaml_dict
        except Exception as e:
            logger.error(f"Load Yaml failed: {e}")
            raise

    @property
    def app_name(self):
        return self._config.get('app_name')

    @property
    def pages(self):
        return self._config.get('pages', [])


class PageNavigator:
    def __init__(self):
        self._navigation_paths = {}
        self.page_graph = defaultdict(dict)
        self.page_elements = defaultdict(dict)

    def build_page_graph(self, pages_config):
        """构建页面导航图"""
        for page_dict in pages_config:
            page_name = page_dict['page']['pageName']
            locators_list = page_dict['page']['locators']

            for locator in locators_list:
                if isinstance(locator, dict):
                    # cache all page elements
                    element_name = locator.get('element')
                    if element_name:
                        self.page_elements[page_name][element_name] = locator
                    # cache all page navigation
                    goto_page = locator.get('goto_page')
                    if goto_page:
                        self.page_graph[page_name][goto_page] = locator
                elif isinstance(locator, list):
                    for loc_dict in locator:
                        element_name = loc_dict.get('element')
                        if element_name:
                            self.page_elements[page_name][element_name] = loc_dict
                        goto_page = loc_dict.get('goto_page')
                        if goto_page:
                            self.page_graph[page_name][goto_page] = loc_dict

    @memoize_navigation_path
    def get_navigation_path(self, start_page, target_page):
        """
        根据BFS算法，获取从当前页面到目标页面的导航路径

        :param start_page: 起始页面名称
        :param target_page: 目标页面名称
        :return navigation_steps: 导航路径
        """
        if start_page == target_page:
            return []

        page_graph = self.page_graph
        queue = deque([(start_page, [])])
        visited = set()

        while queue:
            current_page, path = queue.popleft()
            if current_page == target_page:
                navigation_steps = []
                for step in path:
                    step0, step1 = step
                    current_page_locators = getattr(self, step0)
                    nav_element = next(
                        (ele_obj for ele_name, ele_obj in current_page_locators.items()
                         if ele_obj.get('goto_page') == step1), None)
                    navigation_steps.append(nav_element)
                return navigation_steps

            if current_page in visited:
                continue
            visited.add(current_page)
            for next_page, route_info in page_graph[current_page].items():
                if next_page not in visited:
                    page_path_info = (next_page, path + [(current_page, next_page)])
                    queue.append(page_path_info)

        raise ValueError(f"无法找到从 {start_page} 到 {target_page} 的导航路径")

    def __getattr__(self, current_page):
        if "page_graph" in dir(self):
            cur_page_locator_dict = self.page_graph.get(current_page)
            if cur_page_locator_dict is not None:
                return cur_page_locator_dict
        raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{current_page}'")


class Locator:
    """
    页面元素封装
    """

    def __init__(self, element, by_type=By.XPATH, locator_name='', goto_page=None, desc='', dialog=None):
        """
        封装元素对象
        @param element: 变量名
        @param by_type: 定位方式
        @param locator_name: 定位语句
        @param goto_page: 跳转页面
        @param desc: 描述
        @param dialog: 对话弹框
        """
        self.element = element  # 元素定位值
        self.by_type = by_type  # 定位方式
        self.locator_name = locator_name  # 定位语句，用于ElementOperator属性
        self.goto_page = goto_page
        self.desc = desc
        self.dialog = dialog

    def __str__(self):
        return f'{self.desc}:(By:{self.by_type},element:{self.element})'

    def __repr__(self):
        return f'{self.desc}'


class Activity:
    pass


class AllPage:
    pass
